// InstructionBox.cpp: implementation of the CInstructionBox class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "8085.h"
#include "InstructionBox.h"
#include "GreenTriangle.h"
#include "EnterInstruction.h"
#include "MyException.h"
#include "GlobalFunctions.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CInstructionBox::CInstructionBox()
{
    Init();
}

CInstructionBox::~CInstructionBox()
{

}

void CInstructionBox::DrawItem(LPDRAWITEMSTRUCT lpDIS)
{
	// TODO: Add your code to draw the specified item
	CDC* pDC = CDC::FromHandle(lpDIS->hDC);
	COLORREF defBackground = GetSysColor(COLOR_WINDOW); 
    COLORREF textColor=GetSysColor(COLOR_MENUTEXT); 
    m_cFrame.SetRect(lpDIS->rcItem.left+m_nMarginWidth,lpDIS->rcItem.top,
    lpDIS->rcItem.right,lpDIS->rcItem.bottom);
    m_cTextFrame.SetRect(m_cFrame.left,m_cFrame.top+2,m_cFrame.right,m_cFrame.bottom-1);

    pDC->SetBkMode(OPAQUE);

    CBrush br(defBackground);
    pDC->FillRect(&lpDIS->rcItem,&br);

    BOOL bOutside;
    int nIndex = ItemFromPoint(CPoint(lpDIS->rcItem.left,lpDIS->rcItem.top),bOutside);
    word addr = AddressAtIndex(nIndex);
    if((addr == Get8085Object()->GetIP()))
    {
        m_cGreenTriangle.SetProperty(lpDIS->rcItem.left+25,
            lpDIS->rcItem.top+m_nItemHeight/2,10);
        m_cGreenTriangle.Draw(pDC);
    }
    if(addr==m_nBtAddress)
    {
        m_cBlueTriangle.SetProperty(lpDIS->rcItem.left,
            lpDIS->rcItem.top+m_nItemHeight/2,10);
        m_cBlueTriangle.Draw(pDC);
    }

    if((IsABreakPt(addr)))
    {
        int radius = 11;
        m_cRedRound.SetProperty(lpDIS->rcItem.left+12,
            lpDIS->rcItem.top+2,radius,GetDocument()->IsBkPtEnabled(addr));
        m_cRedRound.Draw(pDC);
    }
    // item has been selected - hilite m_cFrame
    if(m_nSelectedIndex==nIndex)
    {
		if(GetFocus()==this)
		{
			CBrush b(GetSysColor(COLOR_HIGHLIGHT));
			pDC->FillRect(&m_cFrame,&b);
			m_cSelectedFrame = m_cFrame;
			textColor = GetSysColor(COLOR_HIGHLIGHTTEXT);
		}
        m_nSelectedIndex = nIndex;
		COLORREF crHilite = RGB(255-GetRValue(defBackground),
						255-GetGValue(defBackground), 255-GetBValue(defBackground));
		CBrush br(crHilite);
        pDC->SetBkMode(TRANSPARENT);
		pDC->FrameRect(&m_cFrame, &br);
        //textColor = GetSysColor(COLOR_HIGHLIGHTTEXT);
    }

    // select the default highlight colour
    pDC->SetTextColor(textColor);
    pDC->SetBkColor(defBackground);
    pDC->ExtTextOut(lpDIS->rcItem.left+m_nMarginWidth+5,lpDIS->rcItem.top,ETO_CLIPPED,
        m_cTextFrame,m_aDisplay[nIndex].szMnemonic,NULL);
}

void CInstructionBox::Init()
{
    m_nFirstAddress = 0;
    m_nPropertySet = 0;
    m_bRedrawBkgnd = TRUE;
    m_nMarginWidth=40;
    CMenu *pPopup = m_cMenu.GetSubMenu(0);
    if(pPopup)
    {
        pPopup->EnableMenuItem(ID_TOGGLE_BREAKPOINT,MF_BYCOMMAND|MF_ENABLED);
        pPopup->EnableMenuItem(ID_HELPON_DATA,MF_BYCOMMAND|MF_ENABLED);
        pPopup->EnableMenuItem(ID_SHOW_SOURCE_LINE,MF_BYCOMMAND|MF_ENABLED);
    }
    SetMaxSize(GetMemSize());
    m_cBlueTriangle.SetFillColour(RGB(0,236,236));
    m_nBtAddress = 0xffff;    // address where blue triangle will be displayed
}

void CInstructionBox::OnVirtualVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
	if (nSBCode == SB_LINEDOWN)
    {
        if((int)m_nLastAddress+(int)GetInstructionLenAt(m_nLastAddress)>=GetMemSize())
            return;
        m_nFirstAddress +=GetInstructionLenAt(m_nFirstAddress);
        //m_nLastAddress = 
        RefreshStrings(m_nFirstAddress);
    }
	else if (nSBCode == SB_LINEUP)
    {
        if(m_nFirstAddress==0) return;
        int nPrev = m_nSelectedIndex;
        m_nSelectedIndex=0;
        MoveOneAddressUp();
        m_nSelectedIndex=nPrev;
    }
    else if(nSBCode == SB_PAGEDOWN)
    {
        if(!MoveOnePageDown()) return;
    }
    else if(nSBCode == SB_PAGEUP)
    {
        if(!MoveOnePageUp()) return;
    }
    InvalidateRect(NULL);
	//CListBox::OnVScroll(nSBCode, nPos, pScrollBar);
}

void CInstructionBox::OnVirtualKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
    if(GetDocument()->IgnoreInputs()) 
        return;
    switch(nChar)
    {
    case VK_HOME    :   
        if(!MoveToHome()) return;
        //m_nSelectedAddress = m_nFirstAddress =0;
        RefreshStrings();
        break;

    case VK_END     :   
        if(!MoveToEnd()) return;
        break;

    case VK_PRIOR   :  
        if(!MoveOnePageUp()) return;
        break;
    
    case VK_NEXT    :  
        if(!MoveOnePageDown()) return;
        break;

    case VK_UP      :
        if(!MoveOneAddressUp()) return;
        break;

    case VK_DOWN    :
        if(!MoveOneAddressDown()) return;
        break;

    case VK_F9      :
		if(m_bCtrlDown)
			EnableDisableBkPt();
		else
			if(!ToggleBreakPoint()) return;
        break;

    case 0x47       :   // key code for 'G', VK_G did not work, I don't know why
        if(!GotoAddress()) return;
        break;

    default     :
        return;
    }
    InvalidateRect(m_Rect);
	//CListBox::OnKeyUp(nChar, nRepCnt, nFlags);
}



int CInstructionBox::RefreshStrings(int addr,BOOL bCheckModified)
{
    word nNextAddress = addr;
    //m_nFirstAddress = addr;
    for(int i=0; i<m_nItems ; i++)
    {
        sprintf(m_aDisplay[i].szMnemonic,"%04x %s",
            nNextAddress,Get8085Object()->OpcodeToString(nNextAddress));
        m_aDisplay[i].address = nNextAddress;
        nNextAddress  += Get8085Object()->GetInstructionLenAt(nNextAddress);
    }
    m_nLastAddress = nNextAddress;
    if(m_nLastAddress<m_nFirstAddress) m_nLastAddress = m_aDisplay[m_nItems-1].address;
    return 1;
    //Display();
}


int CInstructionBox::MoveOneAddressUp()
{
    if(m_nFirstAddress==0 && m_nSelectedIndex==0) return 0;

    if(m_nSelectedIndex==0)
    {
        word memSize = Get8085Object()->GetMemSize();
        int nSegment = Get8085Object()->WhichSegment(m_nFirstAddress);
        int nPrev;
        for(int addr= Get8085Object()->GetEndAddrInSegment(nSegment-1) ;
            addr<memSize ; )
        {
                nPrev = addr;
                addr += Get8085Object()->GetInstructionLenAt(addr);
                if(addr>=m_nFirstAddress) break;
        }
        //m_nLastAddress = 
        RefreshStrings(nPrev);
        m_nFirstAddress = nPrev;
    }
    else --m_nSelectedIndex;
    return 1;
}

int CInstructionBox::MoveToHome()
{
    if(m_nFirstAddress==0 && m_nSelectedIndex==0) return 0;
    if(m_nFirstAddress==0) 
        m_nSelectedIndex = 0;
    else
    {
        m_nFirstAddress = 0;
        //m_nLastAddress = 
        RefreshStrings();
		m_nSelectedIndex = 0;
    }
    return 1;
}

int CInstructionBox::MoveToEnd()
{
    int memSize = GetMemSize();
    int nSegment = Get8085Object()->WhichSegment(memSize);
    int j;
    int check;

    // make the previous addressess zero
    if((int)m_nLastAddress+(int)GetInstructionLenAt(m_nLastAddress)>=(int)memSize) 
		return 0;

    j = 0;
    //for(int addr= GetEndAddrInSeg(nSegment-1); ; j = (j+1)%GetSegmentSize())

    for(int addr= GetEndAddrInSeg(nSegment); ; j = (j+1)%GetSegmentSize())
    {
        m_nPrev[j] = addr;
        if(addr>memSize)
            break;
        addr += GetInstructionLenAt(addr);
        // check to see if end is reached
        check = m_nPrev[m_nItems-1]+ GetInstructionLenAt(m_nPrev[m_nItems-1]);
        //if(check >= memSize) goto doRest; 
    }
    m_nFirstAddress = m_nPrev[j-m_nItems];
    //m_nLastAddress = 
    RefreshStrings(m_nFirstAddress);
    m_nSelectedIndex = m_nItems-1;
    return 1;

}

int CInstructionBox::MoveOneAddressDown()
{
    int addr = AddressAtIndex(m_nSelectedIndex);
    if( addr >= GetMemSize())
        return 0;
    //if(GetInstructionLenAt(addr)>1)|| addr+GetInstructionLenAt(addr)>=GetMemSize()) 

    if(m_nSelectedIndex < m_nItems-1)
        ++m_nSelectedIndex;
    else
    {
        m_nFirstAddress += GetInstructionLenAt(m_nFirstAddress);
        //m_nLastAddress = 
        RefreshStrings(m_nFirstAddress);
    }
    return 1;
}

int CInstructionBox::MoveOnePageUp()
{
    if(m_nFirstAddress==0 && m_nSelectedIndex==0)  return 0; // do nothing
    if(m_nFirstAddress!=0)
    {
        int nSegment = Get8085Object()->WhichSegment(m_nFirstAddress);
        int i,j;

        while(nSegment>0)
        {
            j = 0;
            for(i= GetEndAddrInSeg(nSegment-1) ; j<GetSegmentSize(); j++ )
            {
                m_nPrev[j] = i;
                i += GetInstructionLenAt(i);
                if(i>=m_nFirstAddress) break;
            }
            // this is required only if each segment is smaller than
            // nHeight i.e., the number of elements displayed in the list box
            if(j>=GetSegmentSize()) 
                --nSegment;
            else break;
                
        }
        if(j>m_nItems) m_nFirstAddress=m_nPrev[j-m_nItems+1];
        else m_nFirstAddress=m_nPrev[0];
        //m_nLastAddress=
        RefreshStrings(m_nFirstAddress);
    }
    else m_nSelectedIndex = 0;
    return 1;
}

int CInstructionBox::MoveOnePageDown()
{
    if(m_nLastAddress>=GetMemSize() && m_nSelectedIndex>=m_nItems-1) 
        return 0;   // do nothing

    int memSize = GetMemSize();
    if((int)m_nLastAddress+(int)GetInstructionLenAt(m_nLastAddress)>=memSize)
        m_nSelectedIndex = m_nItems-1;
    int i,j;

    word addr = m_nLastAddress+GetInstructionLenAt(m_nLastAddress);
    addr=m_nFirstAddress;
    // going twice in this loop ensures that when a page down occurs very near
    // the end of memory, we don't overshoot the memory size
    for(j=i=0; addr<memSize && i<2*m_nItems ; i++,j=(j+1)%GetSegmentSize())
    {
        addr += GetInstructionLenAt(addr);
        m_nPrev[j] = addr;
    }
    m_nFirstAddress = m_nPrev[m_nItems-1];
    //m_nLastAddress = 
    RefreshStrings(m_nFirstAddress);
    return 1;
}

int CInstructionBox::EnterNewInstruction()
{
    CEnterInstruction i;

    i.m_szMemoryLocation.Format("0x%04x",AddressAtIndex(m_nSelectedIndex));
    i.m_szInstruction       = OpcodeToString(AddressAtIndex(m_nSelectedIndex),"0x");
    if(i.DoModal()==IDOK)
    {
        UpdateData(FALSE);
        char szInstruction[64];
        char szNumber[64];
        int address;
        strncpy(szInstruction,i.m_szInstruction,63);
        strncpy(szNumber,i.m_szMemoryLocation,63);
        if(!IsNumber(szNumber,address)) MessageBox("Invalid Number ","Error");
        else
        {
            try
            {
                GetDocument()->GetParserObject()->ParseAndInsertAt(address,szInstruction);
                InvalidateRect(NULL);
            }
            catch (CMyException& e)
            {
                MessageBox(e.GetErrorMessage(),"Exception");
            }
        }
    }
    return 1;
}


int CInstructionBox::ToggleBreakPoint()
{
    word address = AddressAtIndex(m_nSelectedIndex);
	GetView()->ToggleBkPtAtAddress( address );
/*
    if( GetDocument()->DeleteBkPt(address))
        return 1;
    AddBkPt(address);
*/
    return 1;
}

void CInstructionBox::EnterData()
{
    if(EnterNewInstruction())
        Refresh();
}

void CInstructionBox::Adjust(word addr)
{
    if(addr>=m_nLastAddress || addr<m_nFirstAddress)
    {
        m_nFirstAddress = addr;
        //m_nLastAddress = 
        RefreshStrings(m_nFirstAddress);
        InvalidateRect(m_Rect);
    }
}

// is the given address a break point in the list of break points
int CInstructionBox::IsABreakPt(word address)
{
    return GetDocument()->IsABkPt(address);
}


void CInstructionBox::EnableDisableMenuItems()
{
    CMenu *pPopup = m_cMenu.GetSubMenu(0);
    if(pPopup)
    {
        word addr = AddressAtIndex(GetSelectedIndex());
        int state = MF_BYCOMMAND;
        state |= (IsABreakPt(addr)?MF_ENABLED:(MF_DISABLED|MF_GRAYED));
        pPopup->EnableMenuItem(ID_ENABLE_DISABLE_BKPT,state);
    }
}

int CInstructionBox::GetSelectedIndex()
{
    return m_nSelectedIndex;
}


void CInstructionBox::EnableDisableBkPt()
{
    word addr = AddressAtIndex(GetSelectedIndex());
	GetView( )->EnableDisableBkPt( addr );

	return;
    if(IsABreakPt(addr))
    {
        GetDocument()->EnableBkPt(addr,!GetDocument()->IsBkPtEnabled(addr));
        InvalidateRect(NULL);
    }
}

void CInstructionBox::OnShowSourceLine()
{
    // show the current source position
    try
    {
        GetView()->ShowSourcePosition(AddressAtIndex(GetSelectedIndex()));
    }
    catch(CMyException& cm)
    {
        cm.ShowErrorMessage();
    }
}

// show a triangle at the given address in the instruction box
// this corresponds to the line number in the edit window
void CInstructionBox::ShowDisAssemblyPosition(word address)
{
    CDC *pDC = GetDC();

    Adjust(address);
    m_nBtAddress = address;
    m_cBlueTriangle.Draw(pDC,TRUE); // erase the previous one
    m_cBlueTriangle.SetProperty(m_Rect.left,
        m_Rect.top+m_nItemHeight*IndexAtAddress(address)+
        m_nItemHeight/2,10);
    m_cBlueTriangle.Draw(pDC);
}

void CInstructionBox::AddBkPt(word addr)
{
    int nSrcLine=-1;

    // try to get the equivalent source line for this 8085 instruction
    try
    {
        nSrcLine=GetDebugObject().GetSourceLine(addr)-1;
    }
    catch(CMyException& )
    {
    }
    GetDocument()->AddBkPt(addr,nSrcLine);    
}
